# -*- coding: utf-8 -*-
# @version        : 1.0
# @Create Time    : 2021/10/18 22:19 
# @File           : database.py
# @IDE            : PyCharm
# @desc           : SQLAlchemy 部分

"""
导入 SQLAlchemy 部分

安装： pip install sqlalchemy
"""
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.ext.declarative import declared_attr, declarative_base
from sqlalchemy.orm import sessionmaker
from application.settings import SQLALCHEMY_DATABASE_URL, SQLALCHEMY_DATABASE_ENABLE, REDIS_DB_ENABLE
from core.exception import CustomException
from fastapi import Request
from aioredis.client import Redis


def create_async_engine_session(database_url: str):
    """
    创建一个 SQLAlchemy『引擎』

    echo=True：引擎将会把执行的SQL语句打印到控制台，引擎将用repr()函数记录所有语句及其参数列表到日志

    由于SQLAlchemy是多线程，指定check_same_thread=False来让建立的对象任意线程都可使用。这个参数只在用SQLite数据库时设置

    信息：
    默认情况下，假定每个线程将处理一个独立的请求，SQLite 仅允许一个线程与其通信。
    这是为了防止为不同的事物（针对不同的请求）意外共享同一连接。
    但是在 FastAPI 中，使用正常功能（def），一个以上的线程可以与同一请求的数据库交互
    因此我们需要使 SQLite 知道它应该允许使用 connect_args = {“ check_same_thread”：False}。
    此外，我们将确保每个请求在依赖关系中都有自己的数据库连接会话，因此不需要该默认机制。

    相关配置文档：https://docs.sqlalchemy.org/en/14/core/engines.html#database-urls

    database_url  dialect+driver://username:password@host:port/database
    max_overflow 超过连接池大小外最多创建的连接，sqlite数据库不支持
    pool_size=5,     # 连接池大小，sqlite数据库不支持
    pool_timeout=20, # 池中没有连接最多等待的时间，否则报错
    pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收（重置）

    :param database_url: 数据库地址
    :return:
    """
    engine = create_async_engine(
        database_url,
        echo=False,
        pool_pre_ping=True,
        pool_recycle=3600,
        future=True,
        connect_args={}
    )
    return sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=True, class_=AsyncSession)


class Base:
    """将表名改为小写"""

    @declared_attr
    def __tablename__(self):
        # 如果有自定义表名就取自定义，没有就取小写类名
        table_name = self.__tablename__
        if not table_name:
            model_name = self.__name__
            ls = []
            for index, char in enumerate(model_name):
                if char.isupper() and index != 0:
                    ls.append("_")
                ls.append(char)
            table_name = "".join(ls).lower()
        return table_name


"""
创建基本映射类
稍后，我们将继承该类，创建每个 ORM 模型
"""
Model = declarative_base(name='Model', cls=Base)

""" 附上两个SQLAlchemy教程

Python3+SQLAlchemy+Sqlite3实现ORM教程
    https://www.cnblogs.com/jiangxiaobo/p/12350561.html

SQLAlchemy基础知识 Autoflush和Autocommit
    https://www.jianshu.com/p/b219c3dd4d1e
"""


async def db_getter() -> AsyncSession:
    """
    获取关系数据库

    数据库依赖项，它将在单个请求中使用，然后在请求完成后将其关闭。
    """
    if not SQLALCHEMY_DATABASE_ENABLE:
        raise CustomException("请先配置数据库链接并启用！", desc="请启用 application/settings.py: SQLALCHEMY_DATABASE_ENABLE")
    async with create_async_engine_session(SQLALCHEMY_DATABASE_URL)() as session:
        async with session.begin():
            yield session


def redis_getter(request: Request) -> Redis:
    """
    获取关系数据库

    数据库依赖项，它将在单个请求中使用，然后在请求完成后将其关闭。
    """
    if not REDIS_DB_ENABLE:
        raise CustomException("请先配置Redis数据库链接并启用！", desc="请启用 application/settings.py: REDIS_DB_ENABLE")
    return request.app.state.redis
